﻿using Dapper.ExpressionExtension.SqlBuilders;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace Dapper.ExpressionExtension.ExpressionMappers
{
    /// <summary>
    /// 
    /// </summary>
    public static partial class ExpressionMapper
    {
        private static readonly ConcurrentDictionary<string, PropertyInfo> ColumnNameCache = new ConcurrentDictionary<string, PropertyInfo>();

        internal static ConcurrentDictionary<QueryCacheKey, string> QueryCache { get; } = new ConcurrentDictionary<QueryCacheKey, string>();


        internal static readonly Dictionary<string, ISqlBuilder> SqlBuilders = new Dictionary<string, ISqlBuilder>
        {
            ["sqlconnection"] = new SqlServerSqlBuilder(),
            ["sqlceconnection"] = new SqlServerCeSqlBuilder(),
            ["sqliteconnection"] = new SqliteSqlBuilder(),
            ["npgsqlconnection"] = new PostgresSqlBuilder(),
            ["mysqlconnection"] = new MySqlSqlBuilder()
        };

        static ExpressionMapper()
        {
            SqlMapper.TypeMapProvider = CreateMap;

            static SqlMapper.ITypeMap CreateMap(Type t) => new CustomPropertyTypeMap(t,
                (type, columnName) =>
                {
                    var cacheKey = type + columnName;
                    if (ColumnNameCache.TryGetValue(cacheKey, out var propertyInfo))
                    {
                        return propertyInfo;
                    }
                    propertyInfo = type.GetProperties().FirstOrDefault(p => p.GetCustomAttribute<ColumnAttribute>()?.Name == columnName
                                                                            || p.Name == columnName)
                                   ?? new DefaultTypeMap(type).GetMember(columnName)?.Property;
                    ColumnNameCache.TryAdd(cacheKey, propertyInfo);

                    return propertyInfo;
                });
        }

        /// <summary>
        /// sql 日志
        /// </summary>
        public static Action<string> LogReceived;

        private static void LogQuery<T>(string query, [CallerMemberName] string method = null)
            => LogReceived?.Invoke(method != null ? $"{method}<{typeof(T).Name}>: {query}" : query);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="connectionType"></param>
        /// <param name="builder"></param>
        public static void AddSqlBuilder(Type connectionType, ISqlBuilder builder) => AddSqlBuilder(connectionType.Name.ToLower(), builder);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="connectionName"></param>
        /// <param name="builder"></param>
        public static void AddSqlBuilder(string connectionName, ISqlBuilder builder) => SqlBuilders[connectionName] = builder;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="connection"></param>
        /// <returns></returns>
        public static ISqlBuilder GetSqlBuilder(IDbConnection connection)
        {
            var connectionTypeName = connection.GetType().Name;
            var builder = SqlBuilders.TryGetValue(connectionTypeName.ToLower(), out var b) ? b : throw new Exception("get sql builder failed");
            return builder;
        }

        /// <summary>
        /// 
        /// </summary>
        public static Func<Type, ISqlBuilder, object> SqlExpressionFactory = (type, sqlBuilder) =>
        {
            var expr = typeof(SqlExpression<>).MakeGenericType(type);
            return Activator.CreateInstance(expr, sqlBuilder);
        };

        internal static SqlExpression<TEntity> CreateSqlExpression<TEntity>(ISqlBuilder sqlBuilder)
        {
            var expr = SqlExpressionFactory(typeof(TEntity), sqlBuilder);
            return (SqlExpression<TEntity>)expr;
        }
    }
}
